So I am trying to create a simple stopwatch app using react hooks. I imported reactstrap in to help with the css. It took me the longest time to figure this out, but whenever I incremented the state inside my jumbotron tag, the component was not rendering the change. Finally I tried calling the state outside of the jumbotron tag, and it worked immediately. So somehow reactstrap or just bootstrap in general is interfering with hooks. Has anyone else seen this happen?
import React, {useState, useEffect} from "react";
import './stopwatch.css';
import { Jumbotron } from "reactstrap";
function Stopwatch() {
/*utilizing useState to set the state with React Hooks*/
const [mSecs, setMSecs] = useState(0);
const [secs, setSecs] = useState(0);
const [mins, setMins] = useState(0);
/*function to start the timer*/
function startTimer() {
console.log("Start Timer Button clicked!");
//setInterval(() => (setMSecs(mSecs + 1), console.log(mSecs)), 1000);
setMSecs(mSecs + 1);
console.log(mSecs);
}
/*function to stop the timer*/
const stopTimer = () => {
console.log('Stop Button clicked!');
clearInterval();
}
/*function to reset the timer*/
function resetTimer(e){
console.log('Reset Button clicked!');
setMSecs(mSecs + 1);
console.log(mSecs);
}
return(
<header className='container'>
<div className='row justify-content-md-center'>
<div className='col-md-6 col-xs-12'>
<Jumbotron>
<div className='fontgradient'>Fullmetal Alchemist Stopwatch</div>
</Jumbotron>
<div className='jumbotron'>
{/*This is the original state call. For some reason, it will not render the state change here.*/}
<p className='fontgradient'>
{mSecs}
</p>
</div>
{/*Here's where I am calling the state outside the jumbotron tag. It works here.*/}
<p>{mSecs}</p>
{/*when the Start button is clicked, it will run the startTimer function*/}
<button className='btn btn-primary' onClick={startTimer}>Start</button>
{/*when the Stop button is clicked, it will run the stopTimer function*/}
<button className='btn btn-danger' onClick={stopTimer}>Stop</button>
{/*when the Reset button is clicked, it will run the resetTimer function*/}
<button className='btn btn-warning' onClick={resetTimer}>Reset</button>
</div>
</div>
</header>
)
}
export default Stopwatch;
Related
I have an input name address (created by a plugin in WordPress context).
When I type some text in this input, I would like to modify an alpine component (new dropdown component used for autocompletion).
Before I used that :
address.addEventListener('input', _.debounce(event => callApi(event.target.value), 250));
Instead of calling callApi function, i need to act on the toggle.
In the dropdown component, I can add :
#set-title.window="title = $event.detail"
but how from my debounce, call the toggle ?
I should be able to do $dispatch('set-title', 'Hello World!') somewhere ?
I specify that I cannot modify my basic input (perhaps in javascript)
Thanks for help
You can create an event listener inside the Alpine.js component's init() method and mutate the title variable from there:
<div>
<input type="text" id="address" />
</div>
<div x-data="myComponent">
<div x-text="title"></div>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('myComponent', () => ({
title: '',
init() {
const address = document.getElementById('address')
address.addEventListener('input', _.debounce(event => {this.title = event.target.value}, 250)
});
}
}))
})
</script>
I'm facing one issue when clicking on the button this.reloadDatatable() is not called. how can I call reloadDatatable() function from toggle function? below is the demo code for that. Thank you.
<button type="button" #click="addSettingContainer.toggle();"></button>
Alpine.data('initOrderGrid', () => ({
addSettingContainer: {
toggle() {
this.reloadDatatable();
}
},
reloadDatatable: function() {
console.log('a');
},
}));
You cannot access the parent object's scope from a child object without some ugly hacks. However it seem that you want to use an event system: on a button click you want to reload the data table. Alpine.js has builtin event system, we can use the $dispatch function to emit a custom event and the x-on method to capture our custom event:
<div x-data="initOrderGrid"
#reload-data-table.window="reloadDatatable">
</div>
<div x-data>
<button #click="$dispatch('reload-data-table')">Reload data table</button>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('initOrderGrid', () => ({
reloadDatatable() {
console.log('Data table reloaded');
}
}))
})
</script>
How to prevent preopenning a modal when start page rendering?
I guess this occurred from modal state property($modalSwitch) didn't injected and not finished DOM calculating when start rendering.
View
<div wire:click="modalToggle"> //modal toggle
...
</div>
...
<div>
...
<x-jet-dialog-modal wire:model="modalSwitch"> //modal
...
<x-jet-danger-button wire:click="modalToggle">
{{ __('OK') }}
</x-jet-danger-button>
</x-jet-dialog-modal>
...
</div>
Component
...
class ... {
public $modalSwitch = false; //modal state, flase:close, true:open
...
public function modalToggle() // on/off toggle
{
$this->modalSwitch = !$this->modalSwitch;
}
...
}
So I tried with used #isset($modalSwitch) directive rapping over the modal, but still blinking when I reload this page.
Currently I have a useLazyQuery hook which is fired on a button press (part of a search form).
The hook behaves normally, and is only fired when the button is pressed. However, once I've fired it once, it's then fired every time the component re-renders (usually due to state changes).
So if I search once, then edit the search fields, the results appear immediately, and I don't have to click on the search button again.
Not the UI I want, and it causes an error if you delete the search text entirely (as it's trying to search with null as the variable), is there any way to prevent the useLazyQuery from being refetched on re-render?
This can be worked around using useQuery dependent on a 'searching' state which gets toggled on when I click on the button. However I'd rather see if I can avoid adding complexity to the component.
const AddCardSidebar = props => {
const [searching, toggleSearching] = useState(false);
const [searchParams, setSearchParams] = useState({
name: ''
});
const [searchResults, setSearchResults] = useState([]);
const [selectedCard, setSelectedCard] = useState();
const [searchCardsQuery, searchCardsQueryResponse] = useLazyQuery(SEARCH_CARDS, {
variables: { searchParams },
onCompleted() {
setSearchResults(searchCardsQueryResponse.data.searchCards.cards);
}
});
...
return (
<div>
<h1>AddCardSidebar</h1>
<div>
{searchResults.length !== 0 &&
searchResults.map(result => {
return (
<img
key={result.scryfall_id}
src={result.image_uris.small}
alt={result.name}
onClick={() => setSelectedCard(result.scryfall_id)}
/>
);
})}
</div>
<form>
...
<button type='button' onClick={() => searchCardsQuery()}>
Search
</button>
</form>
...
</div>
);
};
You don't have to use async with the apollo client (you can, it works). But if you want to use useLazyQuery you just have to pass variables on the onClick and not directly on the useLazyQuery call.
With the above example, the solution would be:
function DelayedQuery() {
const [dog, setDog] = useState(null);
const [getDogPhoto] = useLazyQuery(GET_DOG_PHOTO, {
onCompleted: data => setDog(data.dog)
})
return (
<div>
{dog && <img src={dog.displayImage} />}
<button
onClick={() => getDogPhoto({ variables: { breed: 'bulldog' }})}
>
Click me!
</button>
</div>
);
}
The react-apollo documentation doesn't mention whether useLazyQuery should continue to fire the query when variables change, however they do suggest using the useApolloClient hook when you want to manually fire a query. They have an example which matches this use case (clicking a button fires the query).
function DelayedQuery() {
const [dog, setDog] = useState(null);
const client = useApolloClient();
return (
<div>
{dog && <img src={dog.displayImage} />}
<button
onClick={async () => {
const { data } = await client.query({
query: GET_DOG_PHOTO,
variables: { breed: 'bulldog' },
});
setDog(data.dog);
}}
>
Click me!
</button>
</div>
);
}
The Apollo Client documentation isn't explicit about this, but useLazyQuery, like useQuery, fetches from the cache first. If there is no change between queries, it will not refetch using a network call. In order to make a network call each time, you can change the fetchPolicy to network-only or cache-and-network depending on your use case (documentation link to the fetchPolicy options). So with a fetchPolicy change of network-only in your example, it'd look like this:
const AddCardSidebar = props => {
const [searching, toggleSearching] = useState(false);
const [searchParams, setSearchParams] = useState({
name: ''
});
const [searchResults, setSearchResults] = useState([]);
const [selectedCard, setSelectedCard] = useState();
const [searchCardsQuery, searchCardsQueryResponse] =
useLazyQuery(SEARCH_CARDS, {
variables: { searchParams },
fetchPolicy: 'network-only', //<-- only makes network requests
onCompleted() {
setSearchResults(searchCardsQueryResponse.data.searchCards.cards);
}
});
...
return (
<div>
<h1>AddCardSidebar</h1>
<div>
{searchResults.length !== 0 &&
searchResults.map(result => {
return (
<img
key={result.scryfall_id}
src={result.image_uris.small}
alt={result.name}
onClick={() => setSelectedCard(result.scryfall_id)}
/>
);
})}
</div>
<form>
...
<button type='button' onClick={() => searchCardsQuery()}>
Search
</button>
</form>
...
</div>
);
};
When using useLazyQuery, you can set nextFetchPolicy to "standby". This will prevent the query from firing again after the first fetch. For example, I'm using the hook inside of a Cart Context to fetch the cart from an E-Commerce Backend on initial load. After that, all the updates come from mutations of the cart.
i am trying to build a Ui component in Reactjs which combines a checkbox and a text input attched to it (instead of a text label) so that if the checkbox is checked , the user can change the text input , and if its unchecked the user will not be able to do so
the final goal is to render outside of the component all of textinputs valus which left checked as a list or as a menu item.
Its should look like this :
Checkbox with Text input
anyone knows how should i do this ? im new to reactjs and got a bit confused how to pass logic between two components(as in here between the checkbox and the text input and between the "combo" component and the outer rendered list) .
thanks in advance !
EDIT1:
well i managed to build the component but i cant make the children call the parent handler (handlerCheckbox , handlerInput)in order to actually make the magic happen.
anything im doing wrong ?
this is the child:
class CheckboxTxtInput extends React.Component{
constructor(props){
super(props);
console.log(props.isChecked)
}
handleCheckboxChild(e) {
this.props.handleCheckbox(e,this.props.id)
}
handleInputChild(e){
this.props.handleInput(e,this.props.id)
}
render(){
return (
<div>
<input type="checkbox" onChange={this.handleCheckboxChild} defaultChecked={this.props.isChecked} />
<input type="text" value={this.props.inputValue} disabled={!this.props.isChecked} onChange={this.handleInputChild}/>
</div>
)
}
}
This is the parent:
export default class Text extends React.Component {
constructor(props) {
super(props);
this.state = {
textItems: [{id:0,inputValue:'text',isChecked:true},{id:1,inputValue:'text',isChecked:true}
,{id:2,inputValue:'text',isChecked:true},{id:3,inputValue:'text',isChecked:true}]
};
this.handleCheckbox = this.handleCheckbox.bind(this);
this.handleInput= this.handleInput.bind(this);
}
handleCheckbox(e,id) {
var stateCopy = Object.assign({}, this.state);
stateCopy.textItems[id].isChecked = e.target.value;
this.setState(stateCopy);
}
handleInput(e,id){
var stateCopy = Object.assign({}, this.state);
stateCopy.textItems[id].text = e.target.value;
this.setState(stateCopy);
}
render () {
return (
<div>
<hr className="divider-long"/>
<UI.sectionDividerLabeled label="Show/Hide Text"/>
<hr className="divider-long"/>
<p>Here you can show\hide your text</p>
<div>
<CheckboxTxtInput id={this.state.textItems[0].id} isChecked={this.state.textItems[0].isChecked}
inputValue={this.state.textItems[0].inputValue} handleInput={this.handleInput}
handleCheckbox={this.handleCheckbox} />
<CheckboxTxtInput id={this.state.textItems[1].id} isChecked={this.state.textItems[1].isChecked}
inputValue={this.state.textItems[1].inputValue} handleInput={this.handleInput}
handleCheckbox={this.handleCheckbox}/>
<CheckboxTxtInput id={this.state.textItems[2].id} isChecked={this.state.textItems[2].isChecked}
inputValue={this.state.textItems[2].inputValue}
handleInput={this.handleInput} handleCheckbox={this.handleCheckbox}/>
<CheckboxTxtInput id={this.state.textItems[3].id} isChecked={this.state.textItems[3].isChecked}
inputValue={this.state.textItems[3].inputValue} handleInput={this.handleInput}
handleCheckbox={this.handleCheckbox}/>
</div>
<RenderText />
</div>
)
}
}
The simplest, React-like way to do this is to have a parent wrapper component - say LabeledCheckbox which contains your Text input and your Checkbox components.
When either of the child components do something, they call a callback provided by the parent, and the parent maintains the state for the two components, passing that state down into the props of both children.
The children in this case would never maintain their own state, instead simply calling callbacks and being prop-fed.
Create one component with checkbox and input field with the state of the checkbox and text field.
And then you can reuse it where you want.
You can do something like this :
class CheckboxTxtInput extends React.Component{
constructor(){
super();
this.state = {
checkbox: false,
inputValue: ""
}
}
handleCheckbox(e){
this.setState({checkbox: e.target.checked})
}
handleInput(e){
this.setState({inputValue: e.target.value})
}
render(){
return (
<div>
<input type="checkbox" onChange={this.handleCheckbox.bind(this)} checked={this.state.checkbox}/>
<input type="text" value={this.state.inputValue} disabled={this.state.checkbox} onChange={this.handleInput.bind(this)}/>
</div>
)
}
}
class Test extends React.Component {
render(){
return (
<div><CheckboxTxtInput /></div>
)
}
}
React.render(<Test />, document.getElementById('container'));
Here is the fiddle.
Hope this helps.