Refactor React Components Code Using Context API - reactjs

I am suppose to refactor this code. The task is to avoid passing username, updateUsername all the way down and without passing props directly. I am suppose to do this without using any third party library or framework.
This is the code
import { useState } from "react";
export default function App() {
const [username, setUsername] = useState("owais");
const [otherUsername, setOtherUsername] = useState("calvin");
return (
<>
<User username={username} updateUsername={setUsername} />
<User username={otherUsername} updateUsername={setOtherUsername} />
</>
);
}
function User({ username, updateUsername }) {
return (
<div>
Username: {username}
<UsernameEditor username={username} updateUsername={updateUsername} />
</div>
);
}
function UsernameEditor({ username, updateUsername }) {
return (
<div>
<input
type="text"
value={username}
onChange={(e) => updateUsername(e.target.value)}
/>
</div>
);
}
I decided to use ContextAPI but I cannot seem to find a way to achieve this without either duplicating the user component and userNameEditor component code or passing the props directly to the component. Any ways I can achieve this?
This is what I currently have
import { createContext, useContext, useState } from "react"
interface AppContextInterface {
username: string;
setUsername: Function;
otherUsername: string;
setOtherUsername: Function;
}
const AppCtx = createContext<AppContextInterface | null>(null);
export default function App() {
const [username, setUsername] = useState("owais");
const [otherUsername, setOtherUsername] = useState("calvin");
const value = { username, setUsername, otherUsername, setOtherUsername };
return (
<AppCtx.Provider value={value}>
<User />
<OtherUser />
</AppCtx.Provider>
);
}
function UsernameEditor() {
const appContext = useContext(AppCtx);
return (
<div>
<input
type="text"
value={appContext?.username}
onChange={(e) => appContext?.setUsername(e.target.value)}
/>
</div>
);
}
function User() {
const appContext = useContext(AppCtx);
return (
<div>
Username: {appContext?.username}
<UsernameEditor />
</div>
);
}
function OtherUserEditor() {
const appContext = useContext(AppCtx);
return (
<div>
<input
type="text"
value={appContext?.otherUsername}
onChange={(e) => appContext?.setOtherUsername(e.target.value)}
/>
</div>
);
}
function OtherUser() {
const appContext = useContext(AppCtx);
return (
<div>
Username: {appContext?.otherUsername}
<OtherUserEditor />
</div>
);
}

Related

React, Calling a function from another component

As stated in the question I want to call a function declared in another component. Here's some example data,
function BookingTable() {
const renderTableData = (startId) => {
let id = startId;
}
}
export default BookingTable;
How do i access the renderTableData from another component?
If the function should be accessible is the child component of the component which has the function. Then you can pass the function through props.
But the best option for this is context api. With that you can access the function in multiple components.
Context api helps you share the states and functions of a component
with other components inside the particular project.
In Filecontext.jsx you can see createContext which helps you in creating a context.
In App.jsx, we have created the states and functions which has to be shared among the components and wrapped the components which can access the datas with that context by importing it.
In Formcomponent.jsx, I am using useContext to use the states and functions created in the App.jsx.
Filecontext.jsx
import { createContext } from 'react'
export const Filecontext = createContext({});
App.jsx
import { Filecontext } from './Contexts/Filecontext';
import { useState } from 'react'
function App() {
const [name, setName] = useState("")
const [email, setEmail] = useState("")
const [mobileno, setMobileno] = useState("")
const showAlert = () => {
alert(`Hello ${name}`);
}
return (
<div className="App">
<Filecontext.Provider value={{ name, setName, email, setEmail, mobileno, setMobileno, showAlert }}>
<Formcomponent />
<Listcomponent />
</Filecontext.Provider>
</div>
);
}
export default App;
Formcomponent.jsx
import { Filecontext } from '../Contexts/Filecontext';
import { useContext } from 'react'
export default function Formcomponent() {
const { setName, setEmail, setMobileno, showAlert } = useContext(Filecontext)
return (
<>
<div className="form-group">
<label>Name : </label>
<input type="text" onChange={(e) => { setName(e.target.value) }} />
</div>
<div className="form-group">
<label>Email : </label>
<input type="email" onChange={(e) => { setEmail(e.target.value) }} />
</div>
<div className="form-group">
<label>Mobile No : </label>
<input type="number" onChange={(e) => { setMobileno(e.target.value) }} />
</div>
<div className="form-group">
<input type="submit" value="submit" onClick={() => { showAlert() }} />
</div>
</>
)
}
function BookingTable() {
const renderTableData = (startId) => {
let id = startId;
}
return (
<BookingTable2 renderTableData={renderTableData} />
)
}
export default BookingTable;
const BookingTable2 = ({renderTableData}) => {
const onClickHandler = () => {
renderTableData()
}
return (
<button onClick={onClickHandler}>Calling func from child component</button>
)
}
export default BookingTable;
Bear in mind that React FC are just JS functions that return JSX (in most cases) so you can't access variables that were declared inside of them from outside of the component. The solution to that problem would be to pass the function as props to the child components.

How to share Child component data into Parent Component uisng context API into Reactjs

I want to share Child Component (Product.js) data into Parent Component (App.js) without button click. I will use useState with context API. I don't know how to passed this data without click event.
Product.js data display into console.log() into App.js component.
App.js
import React from 'react';
import Product from './Product';
function App() {
return (
<div className="App">
<Product />
</div>
);
}
export default App;
Product.js
import React from 'react';
const Product = () => {
return (
<div>
Product Name: <input type="text" />
<br />
Description:- <input type="text" />
</div>
)
}
export default Product
Please help I have no advance knowledge of react.
There are three ways to share information without click event with useContext and useState.
useContext
useState in childComponent
useState in parentComponent
1. useContext
import { createContext, useContext, useState } from 'react';
import React from 'react';
const Context = createContext();
const Product = () => {
const [info, setInfo] = useContext(Context);
return (
<div>
Product Name: <input type="text" value={info.name} onChange={(e) => {
setInfo({ ...info, name: e.target.value });
}} />
<br />
Description:- <input type="text" value={info.desc} onChange={(e) => {
setInfo({ ...info, desc: e.target.value });
}} />
</div>
);
}
function App() {
const [info, setInfo] = useState({
name: '',
desc: ''
});
return (
<div className="App">
<Context.Provider value={[info, setInfo]}>
<Product />
</Context.Provider>
</div>
);
}
export default App;
If you have each component in a file. You have to create the context in a third file and import it from the parent and child component.
2. useState in childComponent
import { useEffect, useState } from 'react';
import React from 'react';
const Product = ({ setParentInfo }) => {
const [info, setInfo] = useState({ name: '', desc: '' });
useEffect(() => {
setParentInfo(info);
}, [info])
return (
<div>
Product Name: <input type="text" value={info.name} onChange={(e) => setInfo({ ...info, name: e.target.value })} />
<br />
Description:- <input type="text" value={info.desc} onChange={(e) => setInfo({ ...info, desc: e.target.value })} />
</div>
)
}
let info = { name: '', desc: '' }
function App() {
return (
<div className="App">
<Product setParentInfo={(newInfo) => {
info = { ...newInfo };
}} />
</div>
);
}
export default App;
3. useState in parentComponent
import { useState } from 'react';
import React from 'react';
const Product = ({ info, setParentInfo }) => {
return (
<div>
Product Name: <input type="text" value={info.name} onChange={(e) => setParentInfo({ ...info, name: e.target.value })} />
<br />
Description:- <input type="text" value={info.desc} onChange={(e) => setParentInfo({ ...info, desc: e.target.value })} />
</div>
)
}
function App() {
const [info, setInfo] = useState({ name: '', desc: '' });
console.log("parent: ", info);
return (
<div className="App">
<Product info={info} setParentInfo={(newInfo) => {
setInfo({ ...newInfo });
}} />
</div>
);
}
export default App;
I hope I've helped you. Have a nice day!
If its a direct child to parent communication, it's better to done this using props. Less code, works fine!
App.js:-
import React from 'react';
import Product from './Product';
function App() {
return (
<div className="App">
<Product
onProductNameChange={productName => console.log(productName)}
onProductDescChange={productDesc => console.log(productDesc)}
/>
</div>
);
}
export default App;
Product.js:-
import React from 'react';
const Product = ({onProductNameChange, onProductDescChange}) => {
return (
<div>
Product Name: <input type="text" onChange={e => onProductNameChange(e.target.value)} />
<br />
Description:- <input type="text" onChange={e => onProductDescChange(e.target.value)} />
</div>
)
}
export default Product
Share information without click event with useContext and useState.
App.js
import React from 'react';
import Store from './Store';
import ProductList from './ProductList';
function App() {
return (
<div className="App">
<Store>
<ProductList />
</Store>
</div>
);
}
export default App;
ProductList.js
import React, { useContext } from 'react';
import { Context } from './Store';
import Product from './Product';
import AddProduct from './AddProduct';
const ProductList = () => {
const [state, setState] = useContext(Context);
console.log(state);
return (
<>
<AddProduct />
<Product />
</>
)
}
export default ProductList
AddProduct.js
import React, { useContext} from 'react';
import { Context} from './Store';
const AddProduct = () => {
const [products, setProducts] = useContext(Context);
return (
<div>
Description:- <input type="text"
onChange={(e) => setProducts({ ...products, Description: e.target.value })}/>
</div>
)
}
export default AddProduct
Product.js
import React, { useContext} from 'react';
import { Context} from './Store';
const Product = () => {
const [products, setProducts] = useContext(Context);
return (
<>
<h2>Product</h2>
<p>Description: {products.Description}</p>
</>
)
}
export default Product
Store.js
import React, { useState } from 'react';
const product = {
Description: '',
};
export const Context = React.createContext();
const Store = ({ children}) => {
const [products, setProducts] = useState(product);
return (
<Context.Provider value={[products, setProducts]}>{children}</Context.Provider>
);
};
export default Store;

How to write a useComponent custom hook in React?

I want to create a custom hook useComponent which returns a JSX.Element that will be rendered elsewhere.
I have tried this:
import { useState} from 'react';
const useComponent = () => {
const [value, setValue] = useState('');
const c = () => {
return <>
<p>Component</p>
<input value={value} onChane={(e) => setValue(e.target.value)} />
</>
}
return {
c,
value,
}
}
export default function App() {
const {c: C} = useComponent();
return (
<div className="App">
<C />
</div>
);
}
but it does not work. Once I try typing on input, nothing happens.
How can I achieve this ?
I know it might be a bad practice to do such a thing, but the reason I want this is to be able to open a global dialog and pass the c component as children to the <Dialog /> component so I can both render c inside the dialog's body and also have access to the [value, setValue] state. So my use case would be something like:
[EDIT]
I also add the whole logic with dialog:
import { createContext, useContext, useState } from "react";
const Test = ({ value, setValue }) => {
return (
<>
<p>Component</p>
<input value={value} onChange={(e) => setValue(e.target.value)} />
</>
);
};
const useComponent = () => {
const [value, setValue] = useState("");
return {
element: <Test value={value} setValue={setValue} />,
value
};
};
const DialogCTX = createContext({});
export function DialogProvider(props) {
const [component, setComponent] = useState(null);
const ctx = {
component,
setComponent
};
return (
<DialogCTX.Provider value={ ctx }>
{props.children}
</DialogCTX.Provider>
);
}
export const useDialog = () => {
const {
component,
setComponent,
} = useContext(DialogCTX);
return {
component,
setComponent,
}
};
const Dialog = () => {
const { component } = useDialog();
return <div>
<p>Dialog</p>
{component}
</div>
}
const Setter = () => {
const {element, value} = useComponent();
const {setComponent} = useDialog();
return <div>
<p>Setter component</p>
<p>{value}</p>
<button onClick={() => setComponent(element)}>Set</button>
</div>
}
export default function App() {
return <div className="App">
<DialogProvider>
<Setter />
<Dialog />
</DialogProvider>
</div>;
}
As you said you want to return a JSX.Element but you actually returning a new component (a new function) every time your hook runs. So you could achieve your goal if you actually declare your component outside your hook and return the rendered one. Here is a working example:
import { useState } from "react";
const Test = ({ value, setValue }) => {
return (
<>
<p>Component</p>
<input value={value} onChange={(e) => setValue(e.target.value)} />
</>
);
};
const useComponent = () => {
const [value, setValue] = useState("");
return {
element: <Test value={value} setValue={setValue} />,
value
};
};
export default function App() {
const { element } = useComponent();
return <div className="App">{element}</div>;
}

React re-renders unnecessary components

I'm new to React, so please explain why this is happens(I guess because I'm re-creating the array of objects, but I'm not sure and don't know how to fix this) and how to fix this this.
I think it's better to show the code, rather than talk about it, so:
App.js
import { useState } from "react";
import Input from "./Input";
let lastId = 2;
function App() {
const [inputsValues, setInputsValues] = useState([
{ id: 1, value: "" },
{ id: 2, value: "" },
]);
const handleChange = (e, id) => {
setInputsValues((prevState) => {
const newState = [...prevState];
const index = newState.findIndex((input) => input.id === id);
newState[index].value = e.target.value;
return newState;
});
};
const addNewInput = () => {
setInputsValues((prevState) => {
const newState = [...prevState];
newState.push({ id: ++lastId, value: "" });
return newState;
});
};
return (
<div className="App">
<div>
<button onClick={addNewInput}>Add new input...</button>
</div>
{inputsValues.map((input) => (
<div className="input-wrap">
<Input
key={input.id}
id={input.id}
value={input.value}
handleChange={handleChange}
/>
</div>
))}
</div>
);
}
export default App;
Input.js
import { useRef } from "react";
const Input = ({ id, value, handleChange }) => {
const renderAmount = useRef(0);
return (
<>
<div> Rendered: {renderAmount.current++}</div>
<input
id={id}
type="text"
value={value}
onChange={(e) => handleChange(e, id)}
/>
</>
);
};
export default Input;
Thanks
Seems like you just have to wrap your Input component in memo so that it only rerenders if its props change. So i imported the memo function in the top and called it on the right side of the assignment, so that it 'wraps' your component and makes it memoized.
import { useRef, memo } from "react";
const Input = memo(({ id, value, handleChange }) => {
const renderAmount = useRef(0);
return (
<>
<div> Rendered: {renderAmount.current++}</div>
<input
id={id}
type="text"
value={value}
onChange={(e) => handleChange(e, id)}
/>
</>
);
});
export default Input;

Set state from props change in react functional component?

I have simple react functional component looks like this following code
import React, { useState } from "react";
import "./styles.css";
export default function App() {
return (
<div className="App">
<Test
children={
<React.Fragment>
<label>Name: </label>
<input type="text" onChange={(e) => setData(e)} />
</React.Fragment>
}
/>
</div>
);
}
export function Test({ children }) {
const [data, setData] = useState("");
return (
<>
<div>{children && children}</div>
<div>{data}</div>
</>
);
}
My question is how to update data state inside Test Component when onChange event triggered?
Here is the sandbox
Hopefully anyone can help me..
Thanks in advance
You dont have to send children that way. React has a specific way of handling children which is more easier to do and maintain. In your case you just have to lift you state up and send the state and callback as props.
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [data, setData] = React.useState('')
return (
<div className="App">
<Test data={data}>
<> // shorthand for ReactFragment
<label>Name: </label>
<input type="text" onChange={(e) => setData(e.target.value)} value={data}/>
</>
</Test>
</div>
);
}
export function Test({ data, children }) {
return (
<>
<div>{children && children}</div>
<div>{data}</div>
</>
);
}
You can use use render props technique for this and you can can keep your pattern intact if you do not prefer major refactoring.
Working Demo - https://codesandbox.io/s/flamboyant-monad-hw2lu?file=/src/App.js
import React, { useState } from "react";
import "./styles.css";
export function Child(props) {
const { setData } = props;
return (
<React.Fragment>
<label>Name: </label>
<input type="text" onChange={(e) => setData(e.target.value)} />
</React.Fragment>
);
}
export default function App() {
return (
<div className="App">
<Test
render={(props) => {
const { setData } = props;
return <Child setData={setData} />;
}}
></Test>
</div>
);
}
export function Test(props) {
const [data, setData] = useState("");
const { render } = props;
return (
<>
<div>{render({ setData })}</div>
<div>{data}</div>
</>
);
}

Resources