Moving/ share functions between components - React.js

Hi,
I have created a component which is storying data in Local Storage.
There are functions (I leave a comment there), which I want to use in another component, that’s why I want to move the function to separate folder like “utils”.
Basically, I want to share functionality between components.

Go to repo
Go to view

The component:

import React, { useEffect, useState } from "react";
import axios from "axios";
import { Row, Col } from "react-grid-system";
import { getProductId } from "../../utils/_utils";
import { Wrapper, H1, ProductUl, ProductLi } from "../../assets/styles/styles";
import { Card, IMG } from "./styles";

const ProductView = ({ match }) => {
  const [products, setProducts] = useState([]);

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        process.env.PUBLIC_URL + "/data/products.json"
      );
      setProducts(result.data);
    };

    fetchData();
  }, []);

  // move code below to seperate folder/ file e.g. _utlis.js

  const productParam = getProductId(match.params.productId);

  const [valueName, setValue] = useState(
    localStorage.getItem(`productName_${productParam}`) || ""
  );
  const [valueNumber, setValueNumber] = useState(
    localStorage.getItem(`productNumber_${productParam}`) || ""
  );
  const [valueDesc, setValueDesc] = useState(
    localStorage.getItem(`productDesc_${productParam}`) || ""
  );

  const [valueImgName_01, setValueImgName_01] = useState(
    localStorage.getItem(`productImgName_01${productParam}`) || ""
  );
  const [valueImgName_02, setValueImgName_02] = useState(
    localStorage.getItem(`productImgName_02${productParam}`) || ""
  );

  useEffect(() => {
    localStorage.setItem(`productName_${productParam}`, valueName);
    localStorage.setItem(`productNumber_${productParam}`, valueNumber);
    localStorage.setItem(`productDesc_${productParam}`, valueDesc);
    localStorage.setItem(`productImgName_01${productParam}`, valueImgName_01);
    localStorage.setItem(`productImgName_02${productParam}`, valueImgName_02);
  }, [
    productParam,
    valueName,
    valueNumber,
    valueDesc,
    valueImgName_01,
    valueImgName_02
  ]);

  const onChangeName = event => setValue(event.target.value);
  const onChangeNumber = event => setValueNumber(event.target.value);
  const onChangeDesc = event => setValueDesc(event.target.value);
  const onChangeImgName01 = event => setValueImgName_01(event.target.value);
  const onChangeImgName02 = event => setValueImgName_02(event.target.value);
  //

  return (
    <>
      {products
        .filter(product => productParam === getProductId(product.number))
        .map(product => {
          const productImg01 = product.images[0];
          const productImg02 = product.images[1];
          return (
            <Wrapper key={product.number}>
              <H1>Product detail</H1>
              <Row>
                <Col lg={6}>
                  <Card>
                    <ProductUl>
                      <ProductLi>
                        <span>Name: </span>
                        <p>{valueName === "" ? product.name : valueName}</p>
                        <input type="text" onChange={onChangeName} />
                      </ProductLi>
                      <ProductLi>
                        <span>Number: </span>
                        <p>
                          {valueNumber === "" ? product.number : valueNumber}
                        </p>
                        <input type="text" onChange={onChangeNumber} />
                      </ProductLi>
                      <ProductLi>
                        <span>Description: </span>
                        <p>
                          {valueDesc === "" ? product.description : valueDesc}
                        </p>
                        <input type="text" onChange={onChangeDesc} />
                      </ProductLi>
                      {productImg01 && (
                        <ProductLi>
                          <span>Image name1: </span>
                          <p>
                            {valueImgName_01 === ""
                              ? productImg01.name
                              : valueImgName_01}
                          </p>
                          <input type="text" onChange={onChangeImgName01} />
                          <IMG src={productImg01.url} alt="product-img" />
                        </ProductLi>
                      )}
                      {productImg02 && (
                        <ProductLi>
                          <span>Image name2: </span>
                          <p>
                            {valueImgName_02 === ""
                              ? productImg02.name
                              : valueImgName_02}
                          </p>
                          <input type="text" onChange={onChangeImgName02} />
                          <IMG src={productImg02.url} alt="product-img" />
                        </ProductLi>
                      )}
                    </ProductUl>
                  </Card>
                </Col>
              </Row>
            </Wrapper>
          );
        })}
    </>
  );
};

export default ProductView;

Thank you. :wink:

Hi @deringmagdalena, yes this is indeed very repetitive; but you might define a hook that wraps useState(), and also updates the local storage whenever the state changes like so:

import { useState, useEffect } from 'react'

export const useLocalStorage = (key, initialValue = '') => {
  const storedValue = window.localStorage.getItem(key)

  const [value, setValue] = useState(
    storedValue === null ? initialValue : storedValue
  )

  useEffect(() => {
    window.localStorage.setItem(key, value)
  }, [key, value])

  return [value, setValue]
}

… and use like this:

const [name, setName] = useLocalStorage('name')
const onNameChange = event => setName(event.target.value)

You might then wrap that in a dedicated component so you don’t have to write out using the hook for every input… like e.g.

import React, { useCallback } from 'react'
import { useLocalStorage } from './util'

export const PersistentInput = ({ name, value: initialValue, ...remainingProps }) => {
  const [value, setValue] = useLocalStorage(name, initialValue)
  const onValueChange = useCallback(event => setValue(event.target.value), [setValue])

  return <input name={name} value={value} onChange={onValueChange} {...remainingProps} />
}
1 Like

Thank you for your time and solutions.
I implemented your code in ProductView component, but it’s not a proper solution, because I store the same value from all inputs in Local Storage. You suggested to define hook that wraps useState(), but then every input has the same value as the value in the Local Storage.

Maybe, I didn’t clearly understand you.
This a ProductView after your suggestions (only 2 inputs):

import React, { useEffect, useState, useCallback } from "react";
import axios from "axios";
import { Row, Col } from "react-grid-system";
import { getProductId } from "../../utils/_utils";
import { useLocalStorage } from "../../utils/_utils";
import { Wrapper, H1, ProductUl, ProductLi } from "../../assets/styles/styles";
// eslint-disable-next-line
import { Card, IMG } from "./styles";

const ProductView = ({
  match,
  name,
  value: initialValue,
  ...remainingProps
}) => {
  const [value, setValue] = useLocalStorage(name, initialValue);
  const onValueChange = useCallback(event => setValue(event.target.value), [
    setValue
  ]);
  const [number, setNumber] = useLocalStorage(name, initialValue);
  const onNumberChange = useCallback(event => setNumber(event.target.value), [
    setNumber
  ]);

  const [products, setProducts] = useState([]);

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        process.env.PUBLIC_URL + "/data/products.json"
      );
      setProducts(result.data);
    };

    fetchData();
  }, [setProducts]);

  const productParam = getProductId(match.params.productId);

  return (
    <>
      {products
        .filter(product => productParam === getProductId(product.number))
        .map(product => {
          return (
            <Wrapper key={product.number}>
              <H1>Product detail</H1>
              <Row>
                <Col lg={6}>
                  <Card>
                    <ProductUl>
                      <ProductLi>
                        <span>Name: </span>
                        <p>{value === "" ? product.name : value}</p>
                        <input
                          name={name}
                          value={value}
                          onChange={onValueChange}
                          {...remainingProps}
                        />
                      </ProductLi>
                      <ProductLi>
                        <span>Number: </span>
                        <p>{number === "" ? product.number : number}</p>
                        <input
                          name={name}
                          value={number}
                          onChange={onNumberChange}
                          {...remainingProps}
                        />
                      </ProductLi>
                    </ProductUl>
                  </Card>
                </Col>
              </Row>
            </Wrapper>
          );
        })}
    </>
  );
};

export default ProductView;


utlis.js

import { useState, useEffect } from "react";

export const useLocalStorage = (key, initialValue = "") => {
  const storedValue = window.localStorage.getItem(key);
  const [value, setValue] = useState(
    storedValue === null ? initialValue : storedValue
  );
  const [number, setNumber] = useState(
    storedValue === null ? initialValue : storedValue
  );
  useEffect(() => {
    window.localStorage.setItem(key, value);
  }, [key, value]);
  return [value, setValue, number, setNumber];
};

What I want to achieve is, when I update input in ProductView component (e.g. name), I want to update dynamically name in ProductsView component.

ProductView - https://prnt.sc/okkvv3
ProductsView - https://prnt.sc/okkw8e

This is the main reason, why I want to move the functionality to shareable function.

Yes, you’re not supposed to use the same hook for all inputs but one for each – hence the PersistentInput component…

<PersistentInput name='name' />
<PersistentInput name='number' />
<PersistentInput name='somethingelse' />

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.