Refresh specific component in React - javascript

I'm using functional component in React and i'm trying to reload the component after button is clicked.
import { useCallback, useState } from 'react';
const ProfileLayout = () => {
const [reload, setReload] = useState(false);
const onButtonClick = useCallback(() => {
setReload(true);
}, []);
return (
{reload && (
<ProfileDetails />
)}
<Button onClick={onButtonClick} />
);
};
export default ProfileLayout;
I'm not able to see again the component after page loaded.
Note: I don't want to use window.location.reload();

Note: I don't want to use window.location.reload();
That is good because that is not the correct way to do this in React. Forcing the browser to reload will lose all of your component or global state.
The correct way to force a component to render is to call this.setState() or call this.forceUpdate().

If you need to force the refresh, then better use a number than a boolean.
const ProfileLayout = () => {
const [reload, setReload] = useState(0);
const onButtonClick = useCallback(() => {
setReload(p => p+1);
}, []);
return (
{Boolean(reload) && (
<ProfileDetails />
)}
);
};

What do you mean by reloading the component? You want to re-render it or you want to make the component fetch the data again? Like "refresh"?
Anyways the way your component is coded the <ProfileDetails /> component will not show up on the first render since you are doing reload && <ProfileDetails />, but reload is initially false. When you click the button then ProfileDetails will appear, but another click on the button won't have any effect since reload is already set to true.
If you want to refresh the data the component uses, then you need to implement a callback that triggers the data fetching.
Edit after clarification by author
const ProfileContainer = (props) => {
// initialProfile is the profile data that you need for your component. If it came from another component, then you can set it when the state is first initialized.
const [ profile, setProfile ] = useState(props.initialProfile);
const loadProfile = useCallback( async () => {
// fetch data from server
const p = await fetch('yourapi.com/profile'); // example
setProfile(p);
}
return (<><ProfileDetails profile={profile} /> <button onClick={loadProfile} /></>)
}
Alternate approach to load the data within the component
const ProfileContainer = (props) => {
const [ profile, setProfile ] = useState(null);
const loadProfile = useCallback( async () => {
// fetch data from server
const p = await fetch('yourapi.com/profile'); // example
setProfile(p);
}
useEffect(() => loadProfile(), []); // Empty dependency so the effect only runs once when component loads.
return (<>
{ /* needed since profile is initially null */
profile && <ProfileDetails profile={profile} />
}
<button onClick={loadProfile} />
</>);
};

Related

How to prevent setting a state variable to another state variable?

I have a controlled component that I call Note. I want its default value to be equal to the selected note (which is set in App.js and passed through as a prop). It seems redundant/bad practice. Here's my code, simplified to the relevant parts. How can I set the default value of textarea to be equal to another state variable?
Edit: Forgot to mention that selectedNote is changed in another component. It works for the state set in useEffect but not for the updates.
App.js
function App(){
const [selectedNote, setSelectedNote] = useState("")
useEffect(() => {
async function fetchData(){
let req = await fetch("http://localhost:9292/notes");
let res = await req.json();
setSelectedNote(res[0])
}
fetchData()
},[])
return (
<Note selectedNote={selectedNote.body}/>
)
}
Note.js
function Note({selectedNote}) {
const [editValue, setEditValue] = useState(selectedNote)
return (
<form>
<textarea value={editValue} onChange={handleChange}>
</textarea>
</form>
)
}
(To clarify, I have no issues if I write const [editValue, setEditValue] = useState("testing123") or some other string)
So ideally you want to lift state up so that the parent component manages the state updates, and the Notes component is as dumb as possible.
In this example the data is loaded into state, and then the notes are built, only receiving an id, some body text which will be their value, and an onChange handler.
When the text is changed, the state is copied, the object in the array (defined by the id) updated, and the new array pushed back into state.
const { useEffect, useState } = React;
const json = '[{"id":1,"body":"Note1"},{"id":2,"body":"Note2"},{"id":3,"body":"Note3"}]';
function mockApi() {
return new Promise(res => {
setTimeout(() => res(json), 2000);
});
}
function Example() {
const [ notes, setNotes ] = useState([]);
const [ selectedNote, setSelectedNote ] = useState(null);
useEffect(() => {
mockApi()
.then(res => JSON.parse(res))
.then(data => setNotes(data));
}, []);
function handleChange(e) {
const { value, dataset: { id } } = e.target;
const copy = [...notes];
copy[id - 1].body = value;
setNotes(copy);
}
function handleClick() {
console.log(JSON.stringify(notes));
}
if (!notes.length) return 'Loading';
return (
<div>
{notes.map(note => {
return (
<Note
key={note.id}
id={note.id}
body={note.body}
handleChange={handleChange}
/>
)
})}
<button onClick={handleClick}>
View state
</button>
</div>
);
}
function Note({ id, body, handleChange }) {
return (
<textarea
data-id={id}
value={body}
onChange={handleChange}
/>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
You can provide a function to useState that will only be invoked once, when the component renders. Use that function to copy the prop value into the Note's private state.
const [editValue, setEditValue] = useState(() => selectedNote)
You may have other problems, such as the prop value being blank on initial render, but this is still usually the most straightforward way to initialize a private state var based on a prop.
If it turns out that blank-initial-state is an insurmountable problem, then you may instead need to set up a useEffect that updates the private state when the prop value changes to a satisfactory value. Something like this:
const [editValue, setEditValue] = useState()
React.useEffect(() => {
// only update state if old is blank & new is not
if(!editValue && selectedNote) setEditValue(selectedNote)
}, [selectedNote])

How do i pass data up from a child web component to it's direct parent element

I need to basically pass a value down to my web component, I will then do some random thing to it, now I want to pass that new value back up to my React Comnponent. How do I do that?
function MyReactcomp() {
const [state, setState] = useState("Justin is cool");
return (
<div className="Wrapper">
<customweb-component state={state} />
</div>
);
}
Now inside the customweb-component, I will change state to "No your not!!". How can I pass this value back up from my web component to
my Rea t Copmponent? I know I can pass a function down because you can only pass strings down
Instead of querying for DOM, you should use React's Ref as shown below:
function MyReactcomp() {
const [state, setState] = useState("Justin is cool");
// Hold the web component reference here
const myWebComp = useRef();
useEffect(() => {
myWebComp.current?.addEventListener('tab-select', (event) => {
setState(Object.keys(adminTabs[event.detail.tabIndex]));
});
}, []);
return (
<div className="Wrapper">
<customweb-component ref={myWebComp} state={state} />
</div>
);
}
And, you if you need to observe for changes in your referenced element, then you can use plain useState to hold reference to the element:
function MyReactcomp() {
const [state, setState] = useState("Justin is cool");
// Hold the web component reference here
const [webComp, setWebComp] = useState(null);
useEffect(() => {
webComp?.addEventListener('tab-select', (event) => {
setState(Object.keys(adminTabs[event.detail.tabIndex]));
});
}, [webComp]);
return (
<div className="Wrapper">
<customweb-component ref={setWebComp} state={state} />
</div>
);
}
If you find doing this too many times, you can abstract this behavior into custom hooks. For example:
function useWebCompProp(ref, initialValue) {
const [state, setState] = useState(initialValue);
useEffect(() => {
ref.current?.addEventListener('onState', (event) => {
// Update the state here
setState(event.detail);
});
}, []);
const setter = (newState) => {
setState(newState);
ref.current?.state = newState;
};
return [state, setter];
}
function useWebCompEvent(eventName, callback) {
// Hold the web component reference here
const myWebComp = useRef();
useEffect(() => {
myWebComp.current?.addEventListener(eventName, callback);
}, []);
return myWebComp;
}
function MyReactcomp() {
const myWebComp = useWebCompEvent(eventName, (event) => {
setState(Object.keys(adminTabs[event.detail.tabIndex]));
});
const [state, setState] = useWebCompProp(myWebComp, "Justin is cool");
return (
<div className="Wrapper">
<customweb-component ref={myWebComp} />
</div>
);
}
I figured out that you need to attach a custom event handler to an element in your Lit-html template. You can then use getElementById or something to that effect and listen for the event in the parent component.
This is a combination of my answer and Harshal Patil's he took into account that using Reacts built-in features for manipulating the DOM is preferable to manipulating it directly.
import React, { useEffect, useRef } from 'react';
function MyReactcomp() {
const [state, setState] = useState("Justin is cool");
const webCompRef = useRef();
useEffect(() => {
webCompRef.addEventListener('tab-select', (event) => {
setState("No he is not!!")
})
}, []}
return (
<div className="Wrapper">
<customweb-component ref={webCompRef} state={state} />
</div>
);
}
This pattern will allow you to pass your data down through the web components attributes. You can then listen for your native or custom events. This is cool because now components can cross frameworks and libraries, if one team is using React and the other is using Angular they can share components and keep there UI in sync.
In react, the transfer of data is 1-way only, i.e., from Parent-to-Child
However, if you need to have a handler in the parent element for some data change in the child element, you can use Lifting Up State
This could help achieving what you need.
If not, please provide more details about the actual case scenario

How to update my useEffect hook when State change happen in a different .js file's component in ReactJS?

I am trying to make an API call in useEffect() and want useEffect() to be called everytime a new data is added in the backend.
I made a custom Button(AddUserButton.js) which adds a new user in backend. I am importing this button in the file (ManageUsers.js) where I am trying to display all the users. I just wanted to make an useState to keep track everytime an add button is clicked and make useEffect refresh according to it. For Example:
const [counter, setCounter] = useState(0);
...
const handleAdd = () => {
setCounter(state => (state+1));
};
...
useEffect(() => {
// fetch data here
...
}, [counter]);
...
return(
<Button onClick = {handleAdd}> Add User </Button>
);
But currently because I have two .js files, I am not sure how to make my logic stated above
work in this case
ManageUsers.js
import AddUserButton from "./AddUserButton";
...
export default function ManageShades() {
...
useEffect(() => {
axios
.get("/api/v1/users")
.then(function (response) {
// After a successful add, store the returned devices
setUsers(response.data);
setGetUserFailed(false);
})
.catch(function (error) {
// After a failed add
console.log(error);
setGetUserFailed(true);
});
console.log("Load User useeffect call")
},[]);
return (
<div>
...
<Grid item xs={1}>
<AddUserButton title = "Add User" />
</Grid>
...
</div>
);
}
AddUserButton.js
export default function AddDeviceButton() {
...
return (
<div>
<Button variant="contained" onClick={handleClickOpen}>
Add a device
</Button>
...
</div>
);
}
A common approach is to pass a callback function to your button component that updates the state of the parent component.
import { useState, useEffect } from "react";
const AddUserButton = ({ onClick }) => {
return <button onClick={onClick} />;
};
export default function Test() {
const [updateCount, setUpdateCount] = useState(false);
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count++);
}, [updateCount]);
return (
<div>
<AddUserButton
onClick={() => {
// do something, e.g. send data to your API
// finally, trigger update
setUpdateCount(!updateCount);
}}
/>
</div>
);
}
So it seems like you are trying to let a child update it's parent's state, an easy way to do this is to let the parent provide the child a callback, which will update the parent's state when called.
const parent = ()=>{
const [count, setCount] = useState(0);
const increCallback = ()=>{setCount(count + 1)};
return (<div>
<child callback={increCallback}/>
</div>);
}
const child = (callback)=>{
return (<button onClick={callback}/>);
}
If you were to tell the ManageUsers component to fetch from the back-end right after the AddUser event is fired, you will almost certainly not see the latest user in the response.
Why? It will take some time for the new user request to be received by the back-end, a little longer for proper security rules to be passed, a little longer for it to be formatted, sanitized, and placed in the DB, and a little longer for that update to be available for the API to pull from.
What can we do? If you manage the users in state - which it looks like you do, based on the setUsers(response.data) - then you can add the new user directly to the state variable, which will then have the user appear immediately in the UI. Then the new user data is asynchronously added to the back-end in the background.
How can we do it? It's a really simple flow that looks something like this (based roughly on the component structure you have right now)
function ManageUsers() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('https://api.com')
.then(res => res.json())
.then(res => setUsers(res));
.catch(err => console.error(err));
}, [setUsers]);
const handleAdd = ({ name, phone, dob }) => {
const newUser = {
name,
phone,
dob
};
setUsers([...users, newUser]);
};
return (
<div>
<UserList data={users} />
<AddUser add={handleAdd} />
</div>
);
}
// ...
function AddUser({ add }) {
const [userForm, setUserForm] = useState({ name: "", phone: "", dob: "" });
return (
// controlled form fields
<button onClick={() => add(userForm)}>Submit</button>
);
}
// ...
function UserList({ data }) {
return (
<>
{data.map(user =>
<p>{user.name></p>
}
</>
);
}
Once the user adds a new user with the "Submit" button, it passes the new user to the "add" function which has been passed down as a prop. Then the user is appended to the users array of the ManageUsers component, instantly populating the latest user data in the UserList component. If we wait for a new fetch request to come through, this will add a delay, and the newest user we just added will not likely come back with the response.

What can I use in functional components to have same behavior as componentDidMount?

My UI was working fine until it was using a class component. Now I am refactoring it to a functional component.
I have to load my UI based on the data I receive from an API handler. My UI will reflect the state of the camera which is present inside a room. Every time the camera is turned on or off from the room, I should receive the new state from the API apiToGetCameraState.
I want the console.log present inside the registerVideoStateUpdateHandlerWrapper to print both on UI load for the first time and also to load every time the video state is changed in the room. However, it doesn't work when the UI is loaded for the first time.
This is how my component looks like:
const Home: React.FunctionComponent<{}> = React.memo(() => {
const [video, setToggleVideo] = React.useState(true);
const registerVideoStateUpdateHandlerWrapper = React.useCallback(() => {
apiToGetCameraState(
(videoState: boolean) => {
// this log does not show up when the UI is loaded for the first time
console.log(
`Video value before updating the state: ${video} and new state is: ${videoState} `
);
setToggleVideo(videoState);
}
);
}, [video]);
React.useEffect(() => {
//this is getting called when the app loads
alert(`Inside use effect for Home component`);
registerVideoStateUpdateHandlerWrapper ();
}, [registerVideoStateUpdateHandlerWrapper ]);
return (
<Grid>
<Camera
isVideoOn={video}
/>
</Grid>
);
});
This was working fine when my code was in class component. This is how the class component looked like.
class Home extends Component {
registerVideoStateUpdateHandlerWrapper = () => {
apiToGetCameraState((videoState) => {
console.log(`ToggleVideo value before updating the state: ${this.state.toggleCamera} and new state is: ${videoState}`);
this.setStateWrapper(videoState.toString());
})
}
setStateWrapper = (toggleCameraUpdated) => {
console.log("Inside setStateWrapper with toggleCameraUpdated:" + toggleCameraUpdated);
this.setState({
toggleCamera: (toggleCameraUpdated === "true" ) ? "on" : "off",
});
}
constructor(props) {
super(props);
this.state = {
toggleCamera: false,
};
}
componentDidMount() {
console.log(`Inside componentDidMount with toggleCamera: ${this.state.toggleCamera}`)
this.registerVideoStateUpdateHandlerWrapper ();
}
render() {
return (
<div>
<Grid>
<Camera isVideoOn={this.state.toggleCamera} />
</Grid>
);
}
}
What all did I try?
I tried removing the useCallback in the registerVideoStateUpdateHandlerWrapper function and also the dependency array from React.useEffect and registerVideoStateUpdateHandlerWrapper. It behaved the same
I tried updating the React.useEffect to have the code of registerVideoStateUpdateHandlerWrapper in it but still no success.
Move registerVideoStateUpdateHandlerWrapper() inside the useEffect() callback like this. If you want to log the previous state when the state changes, you should use a functional update to avoid capturing the previous state through the closure:
const Home = () => {
const [video, setVideo] = useState(false);
useEffect(() => {
console.log('Inside useEffect (componentDidMount)');
const registerVideoStateUpdateHandlerWrapper = () => {
apiToGetCameraState((videoState) => {
setVideo((prevVideo) => {
console.log(`Video value before updating the state: ${prevVideo} and new state is: ${videoState}`);
return videoState;
});
});
};
registerVideoStateUpdateHandlerWrapper();
}, []);
return (
<Grid>
<Camera isVideoOn={video} />
</Grid>
);
};
When you no longer actually need to log the previous state, you should simplify registerVideoStateUpdateHandlerWrapper() to:
const registerVideoStateUpdateHandlerWrapper = () => {
apiToGetCameraState((videoState) => {
setVideo(videoState);
});
};
import React from 'react'
const Home = () => {
const [video, setVideo] = useState(null);
//default video is null, when first load video will change to boolean, when the Camera component will rerender
const registerVideoStateUpdateHandlerWrapper = () => {
apiToGetCameraState((videoState) => {
setVideo(videoState);
});
};
useEffect(() => {
registerVideoStateUpdateHandlerWrapper();
}, []);
return (
<Grid>
<Camera isVideoOn={video} />
</Grid>
);
};
export default Home
componentDidMount() === useEffect()
'useEffect' => import from 'react'
// componentDidMount()
useEffect(() => {
// Implement your code here
}, [])
// componentDidUpdate()
useEffect(() => {
// Implement your code here
}, [ update based on the props, state in here if you mention ])
e.g:
const [loggedIn, setLoggedIn] = useState(false);
useEffect(() => {
// Implement the code here
}, [ loggedIn ]);
the above code will act as equivalent to the componentDidUpdate based on 'loggedIn' state

Re-render a sibling component using hooks

I am new to react and I'm trying to get the one component to re-render from another component.
Here's my code:
const Parent = () => {
return (
<div>
<Child1 />
<Child2 />
</div>
)
}
What I intend to do is update Child1 when there is some trigger from Child2.
One way I can think of is to get the parent component to re-render so both Child1 and Child2 will be updated. I tried to do this by lifting the state but it doesn't seem to re-render each of the child components. Here's the code
const Parent = () => {
const [value, setValue] = useState('')
const handlePost = (newValue) => {
setValue(newValue)
}
return (
<div>
<Child1 />
<Child2 onPost={handlePost} />
</div>
)
}
const Child2 = (props) => {
// This function is executed when there is a trigger.
// In this case, when a post request is made to the server
const onPost() => {
props.handlePost('new value')
}
}
Edit:
The reason why the component(s) needs to be re-rendered is because they are making changes to the API and these changes need to be reflected on the screen. It has nothing to do with any state variables.
Your question is an XY problem. In the example given it does not make sense that Child1 rerenders cause there is no need for it. From the comments your real problem is that you update one API, which is supposed to change the response of another API. If you however already know how the response will change, and that it will change, this can be reflected in one state that changes for both API calls:
function useEntries() {
const [entries, setEntries] = useState([]);
useEffect(() => {
setEntries(getEntries());
}, []);
function addEntry(entry) {
postEntry(entry);
setEntries(prev => [...prev, entry]);
}
return { entries, addEntry };
}
function Parent() {
const { entries, addEntry } = useEntries();
return <>
<Entries entries={entries} />
<AddEntry addEntry={addEntry} />
</>;
}
From the comments in the post, it sounds like you have Child1 presenting results of a GET request (being done in Child1). Child2 can add or modify that state on the server with some kind of request and you want to trigger a re-render in order to make Child1 refresh the state.
The general problem is, that children should only re-render if props or their used contexts change. I see two options how to approach this:
Lift the handling of the requests up into the parent. Put the results of the request as props into the child component you want to refresh.
Make the sibling aware of the request having to reload by setting it to "dirty" in some way. Either through context or routing state around through the parent.
Usually it's best to go with option 1 if the components are not too deeply nested. It could look like this:
const Parent = () => {
const [posts, setPosts] = useState([]);
const fetchNewestPosts = useCallback(async () => {
const fetched = await fetchPosts();
setPosts(fetched);
}, [fetchPosts, setPosts]);
const handleSubmit = useCallback(async (event) => {
const newPost = getValuesFromSubmitEvent(event);
await sendNewPost(newPost);
// you could even set the posts here to what you think the
// send request will result in (see Jonas Wilms answer), like
// setPosts(posts => [newPost, ...posts]);
await fetchNewestPosts();
}, [fetchNewestPosts, getValuesFromSubmitEvent, sendNewPost]);
useEffect(() => {
fetchNewestPosts();
}, [fetchNewestPosts]);
return (
<div>
<Child1 posts={posts} />
<Child2 submitNewPost={submitNewPost} />
</div>
);
);
const Child1 = ({posts}) => {
return (
<ul>{posts.map(post => <li key={post.id}>post.title</li>)}</ul>
);
);
const Child2 = ({submitNewPost}) => {
return (
<form onSubmit={submitNewPost}>...</form>
);
);
As a nice side-effect, Child1 and Child2 now need a lot less logic and can be styled independently of the fetchPosts and sendNewPost functions.
Ciao, lets say that Child1 must be re-rendered on handlePost. Your parent component will be:
const Parent= () => {
const [value, setValue] = useState('')
const [rerender, setrerender] = useState(false)
const handlePost = (newValue) => {
setValue(newValue);
let setrerender_temp = rerender;
setrerender(!setrerender_temp);
}
return (
<div>
<Child1 rerender={rerender} />
<Child2 onPost={handlePost} />
</div>
)
}
Then, in your Child1 component:
import React, { useReducer, useEffect } from 'react';
...
export default function Child1(props) {
const [,forceRender] = useReducer((s) => s+1, 0);
useEffect(() => forceRender(), [props.rerender]);
...
}

Categories